// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'dart:typed_data';

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:web_engine_tester/golden_tester.dart';

import '../common/fake_asset_manager.dart';
import '../common/test_initialization.dart';
import 'utils.dart';

void main() {
  internalBootstrapBrowserTest(() => testMain);
}

// This fragment shader generates some voronoi noise. It uses a pseudo-random
// number generator implemented in the shader itself, so its output is
// deterministic.
const String kVoronoiShaderSksl = r'''
{"sksl": {
  "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform float uTileSize;\n\nvec4 fragColor;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n    return flutter_FragCoord.xy;\n}\n\nfloat FLT_flutter_local_rand(vec2 co)\n{\n    return fract(sin(dot(co, vec2(12.98980045318603515625, 78.233001708984375))) * 43758.546875);\n}\n\nvec2 FLT_flutter_local_fuzzGridPoint(vec2 coordinate)\n{\n    vec2 param = coordinate * 400.0;\n    vec2 param_1 = coordinate * 400.0;\n    return coordinate + vec2((FLT_flutter_local_rand(param) - 0.5) * 0.800000011920928955078125, (FLT_flutter_local_rand(param_1) - 0.5) * 0.800000011920928955078125);\n}\n\nvec3 FLT_flutter_local_getColorForGridPoint(vec2 coordinate)\n{\n    vec2 param = coordinate * 100.0;\n    vec2 param_1 = coordinate * 200.0;\n    vec2 param_2 = coordinate * 300.0;\n    return vec3(FLT_flutter_local_rand(param), FLT_flutter_local_rand(param_1), FLT_flutter_local_rand(param_2));\n}\n\nvoid FLT_main()\n{\n    vec2 uv = FLT_flutter_local_FlutterFragCoord() / vec2(uTileSize);\n    vec2 upperLeft = floor(uv);\n    vec2 upperRight = vec2(ceil(uv.x), floor(uv.y));\n    vec2 bottomLeft = vec2(floor(uv.x), ceil(uv.y));\n    vec2 bottomRight = ceil(uv);\n    vec2 closestPoint = upperLeft;\n    vec2 param_3 = upperLeft;\n    float dist = distance(uv, FLT_flutter_local_fuzzGridPoint(param_3));\n    vec2 param_4 = upperRight;\n    float upperRightDistance = distance(uv, FLT_flutter_local_fuzzGridPoint(param_4));\n    if (upperRightDistance < dist)\n    {\n        dist = upperRightDistance;\n        closestPoint = upperRight;\n    }\n    vec2 param_5 = bottomLeft;\n    float bottomLeftDistance = distance(uv, FLT_flutter_local_fuzzGridPoint(param_5));\n    if (bottomLeftDistance < dist)\n    {\n        dist = bottomLeftDistance;\n        closestPoint = bottomLeft;\n    }\n    vec2 param_6 = bottomRight;\n    float bottomRightDistance = distance(uv, FLT_flutter_local_fuzzGridPoint(param_6));\n    if (bottomRightDistance < dist)\n    {\n        dist = bottomRightDistance;\n        closestPoint = bottomRight;\n    }\n    vec2 param_7 = closestPoint;\n    fragColor = vec4(FLT_flutter_local_getColorForGridPoint(param_7), 1.0);\n}\n\nhalf4 main(float2 iFragCoord)\n{\n      flutter_FragCoord = float4(iFragCoord, 0, 0);\n      FLT_main();\n      return fragColor;\n}\n",
  "entrypoint": "main",
  "stage": 1,
  "uniforms": [
    {
      "array_elements": 0,
      "bit_width": 32,
      "columns": 1,
      "location": 0,
      "name": "uTileSize",
      "rows": 1,
      "type": 10
    }
  ]
}}
''';

Future<void> testMain() async {
  setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);

  const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300);

  late FakeAssetScope assetScope;
  setUp(() {
    assetScope = fakeAssetManager.pushAssetScope();
    assetScope.setAsset('voronoi_shader', ByteData.sublistView(utf8.encode(kVoronoiShaderSksl)));
  });

  tearDown(() {
    fakeAssetManager.popAssetScope(assetScope);
  });

  test('fragment shader', () async {
    final ui.FragmentProgram program = await renderer.createFragmentProgram('voronoi_shader');
    final ui.FragmentShader shader = program.fragmentShader();

    Future<void> drawCircle(String goldenFilename) async {
      final ui.PictureRecorder recorder = ui.PictureRecorder();
      final ui.Canvas canvas = ui.Canvas(recorder, region);
      canvas.drawCircle(const ui.Offset(150, 150), 100, ui.Paint()..shader = shader);

      await drawPictureUsingCurrentRenderer(recorder.endRecording());

      await matchGoldenFile(goldenFilename, region: region);
    }

    shader.setFloat(0, 10.0);
    await drawCircle('fragment_shader_voronoi_tile10px.png');

    // Make sure we can reuse the shader object with a new uniform value.
    shader.setFloat(0, 25.0);
    await drawCircle('fragment_shader_voronoi_tile25px.png');

    // Test reusing a Paint object with the same shader.
    final ui.Paint reusablePaint = ui.Paint()..shader = shader;

    Future<void> drawCircleReusePaint(String goldenFilename) async {
      final ui.PictureRecorder recorder = ui.PictureRecorder();
      final ui.Canvas canvas = ui.Canvas(recorder, region);
      canvas.drawCircle(const ui.Offset(150, 150), 100, reusablePaint);

      await drawPictureUsingCurrentRenderer(recorder.endRecording());

      await matchGoldenFile(goldenFilename, region: region);
    }

    shader.setFloat(0, 10.0);
    await drawCircleReusePaint('fragment_shader_voronoi_tile10px_reuse_paint.png');

    // Make sure we can reuse the shader object with a new uniform value and the same Paint object.
    shader.setFloat(0, 25.0);
    await drawCircleReusePaint('fragment_shader_voronoi_tile25px_reuse_paint.png');
  });
}
